#include "safe_timer.h"

#include <iostream>

SafeTimer::SafeTimer(const std::string& name) noexcept
    : name_(name),
      is_loop_(false),
      is_expired_(true),
      try_to_expire_(false),
      task_remain_time_ms_(0),
      callback_(nullptr) {
}

SafeTimer::~SafeTimer() noexcept {
  TryExpire();
}

std::string SafeTimer::GetName() const noexcept {
  return name_;
}

bool SafeTimer::IsLoop() const noexcept {
  return is_loop_;
}

void SafeTimer::Cancel() noexcept {
  if (is_expired_ || try_to_expire_ || !thread_) {
    return;
  }
  TryExpire();
}

bool SafeTimer::Pause() noexcept {
  if (is_expired_) {
    return false;
  }

  auto now = std::chrono::steady_clock::now();
  auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time_).count();
  auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(end_time_ - now).count();

  if (remaining <= 0) {
    return false;
  }

  Cancel();
  task_remain_time_ms_ = static_cast<uint64_t>(remaining);
  return true;
}

void SafeTimer::Resume() noexcept {
  if (task_remain_time_ms_ > 0 && callback_) {
    Start(task_remain_time_ms_, callback_, false, false);
    task_remain_time_ms_ = 0;
  }
}

bool SafeTimer::IsTimerIdle() const noexcept {
  return is_expired_ && !try_to_expire_;
}

bool SafeTimer::Start(uint64_t interval_in_millis, std::function<void()> callback, bool loop,
                      bool callback_immediately) {
  if (!is_expired_ || try_to_expire_) {
    return false;
  }

  is_expired_ = false;
  is_loop_ = loop;

  DestroyThread();
  thread_ = std::make_unique<std::thread>([this, interval_in_millis, callback, callback_immediately]() {
    if (callback_immediately) {
      callback();
    }

    while (!try_to_expire_) {
      callback_ = callback;
      start_time_ = std::chrono::steady_clock::now();
      end_time_ = start_time_ + std::chrono::milliseconds(interval_in_millis);

      std::unique_lock<std::mutex> lock(mutex_);
      condition_.wait_until(lock, end_time_);

      if (try_to_expire_) {
        break;
      }
      callback();
      if (!is_loop_) {
        break;
      }
    }

    is_expired_ = true;
    try_to_expire_ = false;
  });

  return true;
}

void SafeTimer::TryExpire() noexcept {
  try_to_expire_ = true;
  DestroyThread();
  try_to_expire_ = false;
}

void SafeTimer::DestroyThread() noexcept {
  if (thread_) {
    {
      std::lock_guard<std::mutex> lock(mutex_);
      condition_.notify_all();
    }
    if (thread_->joinable()) {
      thread_->join();
    }
    thread_.reset();
  }
}
